#!/bin/sh

## CryptShotR

## Open and mount a LUKs volume, locally or remotely, before performing a
# backup with rsnapshot.
#
# Author:     demure demeanor
# Repository: https://notabug.org/demure/scripts
# Website:    http://demu.red/
# Blog Post:  http://demu.red/

## This is based on cryptshot by:
# Author:   Pig Monkey (pm@pig-monkey.com)
# Website:  https://github.com/pigmonkey/backups

## Modified to support a GPG encrypted LUKs key file.
# This can backup to the LUKs container either locally, or over the network
# to server where the LUKs container is connected. Supports a flag to
# suppress non-configuration related errors. The script can optionally
# support ssh-agent. GPG decryption relies on the gpg-agnet caching, which
# I frequently refresh for pulling emails in mutt.
#
# It will check if the volume with configured UUID exists locally, and if
# not will check remotely for the volume. If the volume is found it will be
# treated as a LUKs volume and decrypted with the given GPG encrypted LUKs
# key file, and then mounted. The script then runs rsnapshot. After the
# backup is complete the volume is unmounted and closed. Can optionally
# delete the mount point for cleanup.
# 
# This script requires a LUKs+dmcrypt container, as well as a GPG encrypted
# LUKs key file. The container allows for secure storage of backups.
# The encrypted key file protects the key from compromise.
#
# The script should be called with your configured rsnapshot intervals.
# Optionally, '-q' or '-quiet' can be used as the first augment, to silence
# non-configuration errors.

### Conf ### {{{
## Define the UUID of the backup volume.
UUID="9c6a7bec-d6ff-484c-87e0-25b9f038aed1"

## Define the location of the LUKS key file.
KEYFILE="$HOME/backups.gpg"

## Define gpg key, used to decrypt LUKS key file.
GPGPRINT="FA88A5CE"

## Define the mount point for the backup volume.
## This will be created if it does not already exist.
MOUNTPOINT="/mnt/$UUID"

## Any non-zero value here will caused the mount point to be deleted after the
## volume is unmounted.
REMOVEMOUNT=1

## Define the location of rsnapshot.
RSNAPSHOT="/usr/bin/rsnapshot"
### End Conf ### }}}

### Remote Conf ### {{{
## Enable Remote backups
RE_ON=1

## Enable ssh agent
# This is assuming that your key is already sourced like:
# https://notabug.org/demure/dotfiles/src/master/subbash/sshagent
RE_AGENT=1

## Define home routers MAC addr.
RE_HOME="10:FE:ED:E9:63:04"

## Define remote address, for testing.
RE_HOST="tiny-server-of-doom.local"

## Define remote user
RE_USER="backups"
### End Remote Conf ### }}}

### Startup Tests ### {{{
## Exit if no volume is specified.
if [ "$UUID" = "" ]; then
    echo 'No volume specified.'
    exit 78
fi

## Exit if no key file is specified.
if [ "$KEYFILE" = "" ]; then
    echo 'No key file specified.'
    exit 78
fi

## Exit if no gpg finger print is specified.
if [ "$GPGPRINT" = "" ]; then
    echo 'No GPG finger print specified.'
    exit 78
fi

## Exit if no mount point is specified.
if [ "$MOUNTPOINT" = "" ]; then
    echo 'No mount point specified.'
    exit 78
fi

## Exit if no interval was specified.
## Tests about this line should not be effected by Quiet flag
## Why? Well, it's enacted here, and missing configuration is BAD.
if [ "$1" = "" ]; then
    echo "No interval specified."
    exit 64
  elif [ "$1" = "-q" ] || [ "$1" = "--quiet" ]; then
    ## set quiet mode code here.
    ## NOTE: Make quiet mode not effect missing settings or interval.
    QUIET=1

    if [ "$2" = "" ]; then
        echo "No interval specified."
        exit 64
      else
        interval=$2
    fi
  else
    interval=$1
    QUIET=0
fi
### End Startup Tests ### }}}

### Remote Startup Tests ### {{{
if [ "$RE_ON" = "1" ]; then
    if [ "$RE_HOME" = "" ]; then
        echo "No local network router MAC specified."
        exit 73
    fi

    if [ "$RE_HOST" = "" ]; then
        echo "No remote host specified."
        exit 73
    fi

    if [ "$RE_USER" = "" ]; then
        echo "No remote user specified."
        exit 73
    fi
fi
### End Remote Startup Tests ### }}}

### Prep Work ### {{{
## Build the reference to the volume.
volume="/dev/disk/by-uuid/$UUID"

## Create a unique name for the LUKS mapping.
name="crypt-$UUID"

## If the mount point does not exist, create it.
if [ ! -d "$MOUNTPOINT" ]; then
    mkdir $MOUNTPOINT
    # Exit if the mount point was not created.
    if [ $? -ne 0 ]; then
        echo "Failed to create mount point."
        exit 73
    fi
fi

    ### GPG Test ### {{{
    ## Check that GPG key is cached in gpg-agent
    ## First see if SUDO_UID exists.
    # This is needed as cryptshotr-cron -> cryptshotr results in using SUDO_UID
    # but cryptshot run manually has UID
    # http://demu.red/blog/2016/08/how-to-check-if-your-gpg-key-is-in-cache-part-2/
    if [ -z "${SUDO_UID}" ]; then
        gpg_socket="/run/user/${UID}/gnupg/S.gpg-agent"
      else
        gpg_socket="/run/user/${SUDO_UID}/gnupg/S.gpg-agent"
    fi

    ## Test if cached
    if [ -e ${gpg_socket} ]; then
        gpg_test=$(gpg-connect-agent -S ${gpg_socket} 'keyinfo --list' /bye 2>/dev/null | awk 'BEGIN{CACHED=0} /^S/ {if($7==1){CACHED=1}} END{print CACHED}')
      else
        gpg_test=$(gpg-connect-agent 'keyinfo --list' /bye 2>/dev/null | awk 'BEGIN{CACHED=0} /^S/ {if($7==1){CACHED=1}} END{print CACHED}')
    fi

    ## Finally, the graceful failure.
    if [ $gpg_test = "0" ]; then
        if [ "$QUIET" = 0 ]; then
            echo 'GPG key is not cached.'
        fi
        exit 78
    fi
    ### End GPG Test ### }}}

## Load ssh-agent, if enabled.
if [ "$RE_AGENT" = 1 ]; then
    ssh_env="$HOME/.ssh/environment"

    if [ -f ${ssh_env} ]; then
        . "${ssh_env}" >/dev/null
        ps -ef | grep ${SSH_AGENT_PID} | grep ssh-agent$ >/dev/null || {
            echo "PID bad?";
        }
      else
        echo "ENV file bad?"
    fi
fi

## Set the default exit code to 0.
exitcode=0
### End Prep Work ### }}}

## Continue if the volume exists.
if [ -e $volume ]; then
    ### Local Backup ### {{{
    ## Attempt to open the LUKS volume.
    gpg2 --no-permission-warning --no-tty -qd $KEYFILE | cryptsetup luksOpen --key-file=- $volume $name
    ## If the volume was decrypted, mount it. 
    if [ $? -eq 0 ]; then
        mount /dev/mapper/$name $MOUNTPOINT
        ## If the volume was mounted, run the backup.
        if [ $? -eq 0 ]; then
            ## Run rsnapshot
            $RSNAPSHOT $interval
            ## Unmount the volume
            umount $MOUNTPOINT
            ## If the volume was unmounted and the user has requested that the
            ## mount point be removed, remove it.
            if [ $? -eq 0 -a $REMOVEMOUNT -ne 0 ]; then
                rmdir $MOUNTPOINT
            fi
          else
            exitcode=$?
            if [ $"$QUIET" = 0 ]; then
                echo "Failed to mount $volume at $MOUNTPOINT."
            fi
        fi
        ## Close the LUKS volume.
        cryptsetup luksClose $name
      else
        exitcode=$?
        if [ $"$QUIET" = 0 ]; then
            echo "Failed to open $volume with key $KEYFILE."
        fi
    fi
    ### End Local Backup ### }}}
  else
    ### Remote Backup ### {{{
    if [ $RE_ON -ne 0 ]; then
        re_addr="${RE_USER}@${RE_HOST}"
        ## Get MAC of router
        check_mac="$(iwgetid --ap -r)"
        if [ "$RE_HOME" = "$check_mac" ]; then
            ## Check that host is up
            ping -c 3 $RE_HOST 1>/dev/null
            if [ $? = 0 ]; then
                ## Check if drive connected
                check_volume=$(ssh $re_addr "test -e $volume && echo 1 || echo 0" 2>/dev/null)
                if [ $check_volume = 1 ]; then
                    ## Make MOUNTPOINT if not present
                    ssh $re_addr "test -e $MOUNTPOINT || sudo mkdir $MOUNTPOINT"
                    if [ $? = 0 ]; then
                        ## Open LUKs container
                        gpg2 --no-permission-warning --no-tty -qd $KEYFILE | ssh $re_addr "sudo cryptsetup luksOpen --key-file=- $volume $name"
                        if [ $? = 0 ]; then
                            ## Mount LUKs container
                            ssh $re_addr "sudo mount /dev/mapper/$name $MOUNTPOINT"
                            if [ $? = 0 ]; then
                                ## Run rsnapshot
                                ssh $re_addr "sudo $RSNAPSHOT $interval"
                                ## Unmount LUKs container
                                ssh $re_addr "sudo umount $MOUNTPOINT"
                                ## If the volume was unmounted and the user has requested that the
                                ## mount point be removed, remove it.
                                if [ $? -eq 0 -a $REMOVEMOUNT -ne 0 ]; then
                                    ssh $re_addr "sudo rmdir $MOUNTPOINT"
                                    rmdir $MOUNTPOINT
                                fi
                              else
                                exitcode=$?
                                if [ $"$QUIET" = 0 ]; then
                                    echo "Failed to mount remote $volume at $MOUNTPOINT."
                                fi
                            fi
                          else
                            exitcode=$?
                            if [ $"$QUIET" = 0 ]; then
                                echo "Failed to open remote $volume with key $KEYFILE."
                            fi
                        fi
                        ## Close LUKs Container
                        ssh $re_addr "sudo cryptsetup luksClose $name"
                      else
                        exitcode=66
                        if [ $"$QUIET" = 0 ]; then
                            echo "Could not make remote $MOUNTPOINT."
                        fi
                    fi
                  else
                    exitcode=66
                    if [ $"$QUIET" = 0 ]; then
                        echo "Remote volume $UUID not found."
                    fi
                fi
              else
                exitcode=66
                if [ $"$QUIET" = 0 ]; then
                    echo "Host not found."
                fi
            fi
          else
            exitcode=66
            if [ $"$QUIET" = 0 ]; then
                echo "Not on home network."
            fi
        fi
      else
        exitcode=33
        if [ $"$QUIET" = 0 ]; then
            echo "Volume $UUID not found."
        fi
    fi
    ### End Remote Backup ### }}}
fi

exit $exitcode
